package com.buoy.base.aluminumpricespider.service;

import com.buoy.base.aluminumpricespider.config.SpiderProperties;
import com.buoy.base.aluminumpricespider.exception.SpiderException;
import com.buoy.base.aluminumpricespider.mapper.BaseMetalPriceMapper;
import com.buoy.base.aluminumpricespider.model.BaseMetalPrice;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
@Slf4j
public class SpiderService {

    private final RestTemplate restTemplate;
    private final ObjectMapper objectMapper = new ObjectMapper();
    private final SpiderProperties spiderProperties;
    private final BaseMetalPriceMapper baseMetalPriceMapper;
    private PriceService priceService;

    @Autowired
    public SpiderService(SpiderProperties spiderProperties, BaseMetalPriceMapper baseMetalPriceMapper) {
        this.spiderProperties = spiderProperties;
        this.baseMetalPriceMapper = baseMetalPriceMapper;
        this.restTemplate = new RestTemplate();
    }
    
    @Autowired
    public void setPriceService(@Lazy PriceService priceService) {
        this.priceService = priceService;
    }

    /**
     * 爬取上海铝价
     *
     * @return 爬取的铝价数据
     */
    public BaseMetalPrice crawlShanghaiPrice() {
        // 检查今天是否是工作日
        if (!isWorkday()) {
            log.info("今天是周末或节假日，不爬取上海铝价");
            return null;
        }
        try {
            log.info("开始爬取上海铝价...");
            // 解析价格数据
            int price = getShanghaiA00Price();

            // 如果不能计算出平均价，返回null
            if (price == 0) {
                throw new SpiderException("无法获取上海铝价的平均价格");
            }

            // 保存到数据库
            BaseMetalPrice baseMetalPrice = new BaseMetalPrice();
            baseMetalPrice.setPriceSource(spiderProperties.getShanghaiPriceSource());
            baseMetalPrice.setPriceDate(LocalDate.now());
            baseMetalPrice.setAveragePrice(price);
            
            // 保存或更新价格
            BaseMetalPrice savedPrice = saveOrUpdatePrice(baseMetalPrice);
            log.info("上海铝价保存成功，价格: {}", savedPrice.getAveragePrice());

            return savedPrice;

        } catch (Exception e) {
            throw new SpiderException("爬取上海铝价出错: " + e.getMessage(), e);
        }
    }

    /**
     * 爬取长江铝价
     *
     * @return 爬取的铝价数据
     */
    public BaseMetalPrice crawlChangjiangPrice() {
        // 检查今天是否是工作日
        if (!isWorkday()) {
            log.info("今天是周末或节假日，不爬取长江铝价");
            return null;
        }
        try {
            log.info("开始爬取长江铝价...");
            int price = getMaterialAveragePrice(LocalDate.now(), spiderProperties.getChangjiangPriceSource());
            if (price <= 0) {
                throw new SpiderException("无法获取长江铝价");
            }
            // 保存到数据库
            BaseMetalPrice baseMetalPrice = new BaseMetalPrice();
            baseMetalPrice.setPriceSource(spiderProperties.getChangjiangPriceSource());
            baseMetalPrice.setPriceDate(LocalDate.now());
            baseMetalPrice.setAveragePrice(price);

            // 保存或更新价格
            BaseMetalPrice savedPrice = saveOrUpdatePrice(baseMetalPrice);
            log.info("长江铝价保存成功，价格: {}", savedPrice.getAveragePrice());

            return savedPrice;

        } catch (IOException e) {
            throw new SpiderException("爬取长江铝价出错: " + e.getMessage(), e);
        }
    }
    
    /**
     * 保存或更新价格数据
     * 
     * @param baseMetalPrice 价格数据
     * @return 保存或更新后的价格数据
     */
    private BaseMetalPrice saveOrUpdatePrice(BaseMetalPrice baseMetalPrice) {
        // 检查数据库中是否已存在当日记录
        BaseMetalPrice existingPrice = baseMetalPriceMapper
                .findByPriceDateAndPriceSource(baseMetalPrice.getPriceDate(), baseMetalPrice.getPriceSource());
        
        if (existingPrice != null) {
            baseMetalPrice.setId(existingPrice.getId());
            log.info("更新{}铝价记录，id: {}", 
                    baseMetalPrice.getPriceSource().equals(spiderProperties.getShanghaiPriceSource()) ? "上海" : "长江", 
                    baseMetalPrice.getId());
            baseMetalPriceMapper.updateById(baseMetalPrice);
        } else {
            log.info("创建新的{}铝价记录", 
                    baseMetalPrice.getPriceSource().equals(spiderProperties.getShanghaiPriceSource()) ? "上海" : "长江");
            baseMetalPriceMapper.insert(baseMetalPrice);
        }
        
        return baseMetalPrice;
    }

    /**
     * 获取长江铝价
     *
     * @param date 日期
     * @param source 来源（changjiang）
     * @return 铝价
     */
    private int getMaterialAveragePrice(LocalDate date, String source) throws IOException {
        if (date == null) {
            date = LocalDate.now();
        }

        // 格式化日期为 yyyyMMdd
        String formattedDate = date.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        String href = String.format("%s%s.htm", spiderProperties.getChangjiangPriceUrl(), formattedDate);

        Document doc = Jsoup.connect(href)
                .userAgent(spiderProperties.getUserAgent())
                .timeout(spiderProperties.getTimeout())
                .get();

        Elements elements = doc.select("head>title");
        if (elements.isEmpty()) {
            log.error("未找到价格标题元素");
            return 0;
        }

        String price = elements.get(0).html();
        Pattern pattern = Pattern.compile("\\d{5}");
        Matcher matcher = pattern.matcher(price);

        if (matcher.find()) {
            price = price.substring(matcher.start(), matcher.end());
            return Integer.parseInt(price);
        } else {
            log.error("未在标题中找到有效的价格数字: {}", price);
            return 0;
        }
    }
    
    /**
     * 获取指定日期的长江铝价前三天均价
     * 
     * @param date 指定日期
     * @return 前三天均价，如果获取失败则返回null
     */
    public Integer getChangjiangThreeDayAvgPrice(LocalDate date) {
        try {
            log.info("从长江铝价网页获取{}的前三天均价", date);
            
            // 获取长江铝价页面
            Document doc = getChangjiangPriceDocument();
            if (doc == null) {
                return null;
            }
            
            // 获取指定日期对应的行
            Element targetRow = findRowByDate(doc, date);
            if (targetRow != null) {
                // 从行中获取前三天均价
                if (targetRow.children().size() >= 5) {
                    Element threeDayCell = targetRow.children().get(4); // 第5列是前三日均价
                    if (threeDayCell != null) {
                        String threeDayText = threeDayCell.text().trim();
                        // 提取价格数字，去除"￥"和空格
                        threeDayText = threeDayText.replace("￥", "").replace(" ", "").trim();
                        try {
                            Integer threeDayAvg = Integer.parseInt(threeDayText);
                            log.info("从长江铝价表格中获取到{}的前三天均价: {}", date, threeDayAvg);
                            return threeDayAvg;
                        } catch (NumberFormatException e) {
                            log.warn("解析长江铝价前三天均价数字失败: {}", threeDayText);
                        }
                    }
                }
            } else {
                log.info("未找到日期{}对应的行", date);
            }
            
            // 如果没有找到指定日期的行或解析失败
            log.info("长江铝价网页中未找到{}的前三天均价信息", date);
            return null;
            
        } catch (Exception e) {
            log.warn("从长江铝价网页获取{}的前三天均价失败: {}", date, e.getMessage());
            return null;
        }
    }
    
    /**
     * 获取指定日期的长江铝价前五天均价
     * 
     * @param date 指定日期
     * @return 前五天均价，如果获取失败则返回null
     */
    public Integer getChangjiangFiveDayAvgPrice(LocalDate date) {
        try {
            log.info("从长江铝价网页获取{}的前五天均价", date);
            
            // 获取长江铝价页面
            Document doc = getChangjiangPriceDocument();
            if (doc == null) {
                return null;
            }
            
            // 获取指定日期对应的行
            Element targetRow = findRowByDate(doc, date);
            if (targetRow != null) {
                // 从行中获取前五天均价
                if (targetRow.children().size() >= 6) {
                    Element fiveDayCell = targetRow.children().get(5); // 第6列是前五日均价
                    if (fiveDayCell != null) {
                        String fiveDayText = fiveDayCell.text().trim();
                        // 提取价格数字，去除"￥"和空格
                        fiveDayText = fiveDayText.replace("￥", "").replace(" ", "").trim();
                        try {
                            Integer fiveDayAvg = Integer.parseInt(fiveDayText);
                            log.info("从长江铝价表格中获取到{}的前五天均价: {}", date, fiveDayAvg);
                            return fiveDayAvg;
                        } catch (NumberFormatException e) {
                            log.warn("解析长江铝价前五天均价数字失败: {}", fiveDayText);
                        }
                    }
                }
            } else {
                log.info("未找到日期{}对应的行", date);
            }
            
            // 如果没有找到指定日期的行或解析失败，尝试直接访问该日期的页面
            String formattedDate = date.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
            String href = String.format("%s%s.htm", spiderProperties.getChangjiangPriceUrl(), formattedDate);
            
            Document specificDoc = Jsoup.connect(href)
                    .userAgent(spiderProperties.getUserAgent())
                    .timeout(spiderProperties.getTimeout())
                    .get();
            
            // 查找包含"五天均价"或"5天均价"的元素
            Elements fiveDayElements = specificDoc.getElementsContainingOwnText("五天均价");
            if (fiveDayElements.isEmpty()) {
                fiveDayElements = specificDoc.getElementsContainingOwnText("5天均价");
            }
            if (fiveDayElements.isEmpty()) {
                fiveDayElements = specificDoc.getElementsContainingOwnText("前五日");
            }
            
            if (!fiveDayElements.isEmpty()) {
                // 从元素中提取价格数字
                String fiveDayText = fiveDayElements.first().text();
                Pattern pattern = Pattern.compile("\\d{5}");
                Matcher matcher = pattern.matcher(fiveDayText);
                if (matcher.find()) {
                    Integer fiveDayAvg = Integer.parseInt(matcher.group());
                    log.info("从长江铝价网页文本中获取到{}的前五天均价: {}", date, fiveDayAvg);
                    return fiveDayAvg;
                }
            }
            
            log.info("长江铝价网页中未找到{}的前五天均价信息", date);
            return null;
            
        } catch (Exception e) {
            log.warn("从长江铝价网页获取{}的前五天均价失败: {}", date, e.getMessage());
            return null;
        }
    }
    
    /**
     * 获取长江铝价网页文档
     * 
     * @return 网页文档，如果获取失败则返回null
     */
    private Document getChangjiangPriceDocument() {
        try {
            // 先访问当前日期的页面，这个页面包含多天历史数据
            LocalDate today = LocalDate.now();
            String formattedDate = today.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
            String href = String.format("%s%s.htm", spiderProperties.getChangjiangPriceUrl(), formattedDate);
            
            return Jsoup.connect(href)
                    .userAgent(spiderProperties.getUserAgent())
                    .timeout(spiderProperties.getTimeout())
                    .get();
        } catch (Exception e) {
            log.error("获取长江铝价网页文档失败: {}", e.getMessage());
            return null;
        }
    }
    
    /**
     * 根据日期在表格中查找对应的行
     * 
     * @param doc 网页文档
     * @param date 日期
     * @return 找到的行元素，如果未找到则返回null
     */
    private Element findRowByDate(Document doc, LocalDate date) {
        if (doc == null || date == null) {
            return null;
        }
        
        // 格式化日期为 MM-dd
        String formattedDate = date.format(DateTimeFormatter.ofPattern("MM-dd"));
        
        // 获取表格中的所有行
        Elements rows = doc.select("table.table tbody tr");
        
        // 遍历行查找匹配的日期
        for (Element row : rows) {
            Element dateCell = row.child(0);
            if (dateCell != null) {
                Element dateLink = dateCell.selectFirst("a");
                if (dateLink != null) {
                    String dateText = dateLink.text().trim();
                    // 去除日期中的空格
                    dateText = dateText.replace(" ", "");
                    if (dateText.equals(formattedDate)) {
                        return row;
                    }
                }
            }
        }
        
        return null;
    }

    /**
     * 获取上海铝价
     *
     * @return 铝价
     */
    private int getShanghaiA00Price() throws IOException {
        WebClient browser = new WebClient();
        try {
            browser.getOptions().setCssEnabled(false);
            browser.getOptions().setJavaScriptEnabled(true);
            browser.getOptions().setThrowExceptionOnScriptError(false);
            browser.getOptions().setTimeout(spiderProperties.getTimeout()); // 设置超时时间

            // 获取页面
            HtmlPage htmlPage = browser.getPage(spiderProperties.getShanghaiPriceUrl());
            // 设置等待js的加载时间
            browser.waitForBackgroundJavaScript(spiderProperties.getTimeout());

            // 使用xml的方式解析获取到jsoup的document对象
            Document doc = Jsoup.parse(htmlPage.asXml());

            Elements elementsByAttributeValue = doc.getElementsByAttributeValue("product_name", "SMM A00 Aluminum Ingot");
            if (elementsByAttributeValue.isEmpty()) {
                log.error("未找到产品行: SMM A00 Aluminum Ingot");
                return 0;
            }

            Element tr = elementsByAttributeValue.get(0).child(0);
            if (tr.children().size() < 7) {
                log.error("表格行列数不足");
                return 0;
            }

            // 获取价格
            Element td = tr.children().get(2);
            Element div = td.children().get(0);
            String text = div.child(0).text().replace(",", "");

            // 获取日期
            Element td6 = tr.children().get(6);
            Element div6 = td6.children().get(0);
            String text6 = div6.child(0).text();

            // 定义输入格式（英文月份）
            DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern("MMM dd, yyyy", Locale.ENGLISH);

            // 解析和格式化
            LocalDate date = LocalDate.parse(text6, inputFormatter);
            if (date.equals(LocalDate.now())) {
                return Integer.parseInt(text);
            } else {
                log.warn("获取的铝价日期不是今天: {}, 今天: {}", date, LocalDate.now());
                return 0;
            }

        } finally {
            browser.close(); // 确保关闭浏览器
        }
    }
    
    /**
     * 获取上海铝价前三天均价
     * 
     * @return 前三天均价，如果获取失败则返回null
     */
    public Integer getShanghaiThreeDayAvgPrice() {
        WebClient browser = new WebClient();
        try {
            log.info("从上海铝价网页获取前三天均价");
            browser.getOptions().setCssEnabled(false);
            browser.getOptions().setJavaScriptEnabled(true);
            browser.getOptions().setThrowExceptionOnScriptError(false);
            browser.getOptions().setTimeout(spiderProperties.getTimeout());

            // 获取页面
            HtmlPage htmlPage = browser.getPage(spiderProperties.getShanghaiPriceUrl());
            browser.waitForBackgroundJavaScript(spiderProperties.getTimeout());

            // 使用xml的方式解析获取到jsoup的document对象
            Document doc = Jsoup.parse(htmlPage.asXml());
            
            // 查找包含"三天均价"或"3天均价"的元素
            Elements threeDayElements = doc.getElementsContainingOwnText("三天均价");
            if (threeDayElements.isEmpty()) {
                threeDayElements = doc.getElementsContainingOwnText("3天均价");
            }
            
            if (!threeDayElements.isEmpty()) {
                // 从元素中提取价格数字
                String threeDayText = threeDayElements.first().text();
                Pattern pattern = Pattern.compile("\\d{5}");
                Matcher matcher = pattern.matcher(threeDayText);
                if (matcher.find()) {
                    Integer threeDayAvg = Integer.parseInt(matcher.group());
                    log.info("从上海铝价网页获取到前三天均价: {}", threeDayAvg);
                    return threeDayAvg;
                }
            }
            
            log.info("上海铝价网页中未找到前三天均价信息");
            return null;
            
        } catch (Exception e) {
            log.warn("从上海铝价网页获取前三天均价失败: {}", e.getMessage());
            return null;
        } finally {
            browser.close(); // 确保关闭浏览器
        }
    }
    
    /**
     * 获取上海铝价前五天均价
     * 
     * @return 前五天均价，如果获取失败则返回null
     */
    public Integer getShanghaiFiveDayAvgPrice() {
        WebClient browser = new WebClient();
        try {
            log.info("从上海铝价网页获取前五天均价");
            browser.getOptions().setCssEnabled(false);
            browser.getOptions().setJavaScriptEnabled(true);
            browser.getOptions().setThrowExceptionOnScriptError(false);
            browser.getOptions().setTimeout(spiderProperties.getTimeout());

            // 获取页面
            HtmlPage htmlPage = browser.getPage(spiderProperties.getShanghaiPriceUrl());
            browser.waitForBackgroundJavaScript(spiderProperties.getTimeout());

            // 使用xml的方式解析获取到jsoup的document对象
            Document doc = Jsoup.parse(htmlPage.asXml());
            
            // 查找包含"五天均价"或"5天均价"的元素
            Elements fiveDayElements = doc.getElementsContainingOwnText("五天均价");
            if (fiveDayElements.isEmpty()) {
                fiveDayElements = doc.getElementsContainingOwnText("5天均价");
            }
            
            if (!fiveDayElements.isEmpty()) {
                // 从元素中提取价格数字
                String fiveDayText = fiveDayElements.first().text();
                Pattern pattern = Pattern.compile("\\d{5}");
                Matcher matcher = pattern.matcher(fiveDayText);
                if (matcher.find()) {
                    Integer fiveDayAvg = Integer.parseInt(matcher.group());
                    log.info("从上海铝价网页获取到前五天均价: {}", fiveDayAvg);
                    return fiveDayAvg;
                }
            }
            
            log.info("上海铝价网页中未找到前五天均价信息");
            return null;
            
        } catch (Exception e) {
            log.warn("从上海铝价网页获取前五天均价失败: {}", e.getMessage());
            return null;
        } finally {
            browser.close(); // 确保关闭浏览器
        }
    }

    /**
     * 判断今天是否是工作日（简化逻辑，仅判断是否是周末）
     *
     * @return 是否是工作日
     */
    private boolean isWorkday() {
        LocalDate today = LocalDate.now();
        DayOfWeek dayOfWeek = today.getDayOfWeek();

        // 首先判断是否是周末
        if (dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY) {
            // 如果是周末，再通过API查询是否是调休上班日
            try {
                String apiUrl = spiderProperties.getHolidayApiUrl() + today.toString();
                ResponseEntity<String> response = restTemplate.getForEntity(apiUrl, String.class);
                if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
                    HolidayResponse holidayResponse = objectMapper.readValue(response.getBody(), HolidayResponse.class);
                    // type: 0 工作日, 1 周末, 2 节日, 3 调休
                    // 如果是调休上班 (type == 3)，则认为是工作日
                    if (holidayResponse.getCode() == 0 && holidayResponse.getHolidayType() != null && holidayResponse.getHolidayType().getType() == 3) {
                        log.info("今天是调休上班日: {}", today);
                        return true;
                    }
                    // 其他周末情况（包括正常周末 type == 1 和 节日且是周末 type == 2 ），认为不是工作日
                    log.info("今天是周末或节假日（API判断）: {}, API返回类型: {}", today, holidayResponse.getHolidayType() != null ? holidayResponse.getHolidayType().getType() : "null");
                    return false;
                } else {
                    log.warn("调用节假日API失败，状态码: {}, 响应体: {}. 按周末处理。", response.getStatusCode(), response.getBody());
                    return false; // API调用失败，按周末处理
                }
            } catch (Exception e) {
                log.error("调用节假日API出错: {}. 按周末处理。", e.getMessage(), e);
                return false; // 异常情况，按周末处理
            }
        }
        
        // 如果不是周末，则直接认为是工作日
        return true;
    }
    
    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    private static class HolidayResponse {
        private int code;
        private HolidayType holidayType;
    }
    
    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    private static class HolidayType {
        private int type; // 节假日类型，分别表示 0工作日、1周末、2节日、3调休。
        private String name; // 类型名称
        private int week; //周几
    }
} 